package mutate

import (
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"go.uber.org/zap"
	"reflect"
	"testing"
)

var templateWithAContainer = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{Data: `spec:
  containers:
    - name: container_mutated
      image: imagemut
      env:
        - name: env_name_mutated
          value: '_mutated'
        - name: ENV_10_mutated
          value: '_mutated'
      resources:
        limits:
          cpu: '55_mutated'
          memory: '55_mutated'
        requests:
          cpu: 55_mutated
          memory: 55_mutated
      args:
        - arg1mut
      securityContext:
        capabilities:
          drop:
            - ALL_mutated`,
	},
}

var templateWithContainers = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `spec:
      containers:
        - name: container_mutated
          image: image-1
          env:
            - name: ENV_1_ADD
              value: 'false'
            - name: ENV_2
              value: 'false'
        - name: some-other-container
          image: image-2
          resources:
            limits:
              cpu: '51m'
              memory: '51Mi'
            requests:
              cpu: 50m
              memory: 50Mi`,
	},
}

var templateWithContainersWithIntersections = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `spec:
      containers:
        - name: container_mutated
          image: image-1
          env:
            - name: ENV_1_ADD
              value: 'false'
            - name: ENV_2
              value: 'false'
        - name: some-other-container
          image: image-2
          resources:
            limits:
              cpu: '51m'
              memory: '51Mi'
            requests:
              cpu: 50m
              memory: 50Mi
        - name: container_mutated
          image: image-1-REPLACED
          env:
            - name: ENV_1_ADD-REPLACED
              value: 'false-REPLACED'
            - name: ENV_2-REPLACED
              value: 'false-REPLACED'
`,
	},
}

var templateWithAVolume = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `spec:
      volumes:
        - name: volume_mutated1
          configMap:
            name: istio`,
	},
}

var templateWithAVolumeWithIntersection = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `spec:
      volumes:
        - name: volume_mutated1
          configMap:
            name: istio
        - name: volume_mutated1
          configMap:
            name: istio-REPLACED`,
	},
}

var templateWithVolumes = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `spec:
      volumes:
        - name: volume_mutated1
          configMap:
            name: istio
        - name: volume_mutated2
          hostPath:
            path: /data
            type: Directory
        - name: volume_mutated3
          secret:
            secretName: mysecret
            optional: true`,
	},
}

var templateWithVolumesWithIntersections = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `spec:
      volumes:
        - name: volume_mutated1
          configMap:
            name: istio
        - name: volume_mutated2
          hostPath:
            path: /data
            type: Directory
        - name: volume_mutated1
          configMap:
            name: istio-REPLACED
          secret:
            secretName: mysecret
            optional: true`,
	},
}

var templateWithAnAnnotation = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation`,
	},
}

var templateWithAnnotations = v1alpha1.Template{
	Spec: v1alpha1.TemplateSpec{
		Data: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation
    annotation_mutated2: another-val-after-mutation`,
	},
}

func TestMutateController_getRendersOldLogic(t *testing.T) {
	type args struct {
		obj    map[string]interface{}
		config v1alpha1.MutationConfig
	}
	tests := []struct {
		name string
		args args

		templates map[string]v1alpha1.Template

		want []util.RenderItem
	}{
		{
			name: "merge_mutation_with_a_container",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation cfg",
					UpdateStrategy: MergeStrategy,
					Containers:     []string{"some-ns/some-templ/container_mutated"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAContainer},
			want: []util.RenderItem{{Render: `spec:
  containers:
  - args:
    - arg1mut
    env:
    - name: env_name_mutated
      value: _mutated
    - name: ENV_10_mutated
      value: _mutated
    image: imagemut
    name: container_mutated
    resources:
      limits:
        cpu: 55_mutated
        memory: 55_mutated
      requests:
        cpu: 55_mutated
        memory: 55_mutated
    securityContext:
      capabilities:
        drop:
        - ALL_mutated
`}},
		},
		{
			name: "merge_mutation_with_containers",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation cfg",
					UpdateStrategy: MergeStrategy,
					Containers: []string{"some-ns/some-templ/container_mutated",
						"some-ns/some-templ/some-other-container"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithContainers},
			want: []util.RenderItem{{
				Render: `spec:
  containers:
  - env:
    - name: ENV_1_ADD
      value: "false"
    - name: ENV_2
      value: "false"
    image: image-1
    name: container_mutated
`},
				{Render: `spec:
  containers:
  - image: image-2
    name: some-other-container
    resources:
      limits:
        cpu: 51m
        memory: 51Mi
      requests:
        cpu: 50m
        memory: 50Mi
`}},
		},
		{
			name: "merge_mutation_with_containers_with_intersections",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation cfg",
					UpdateStrategy: MergeStrategy,
					Containers: []string{"some-ns/some-templ/container_mutated",
						"some-ns/some-templ/some-other-container"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithContainersWithIntersections},
			want: []util.RenderItem{{
				Render: `spec:
  containers:
  - env:
    - name: ENV_1_ADD
      value: "false"
    - name: ENV_2
      value: "false"
    image: image-1
    name: container_mutated
`},
				{Render: `spec:
  containers:
  - env:
    - name: ENV_1_ADD-REPLACED
      value: false-REPLACED
    - name: ENV_2-REPLACED
      value: false-REPLACED
    image: image-1-REPLACED
    name: container_mutated
`},
				{Render: `spec:
  containers:
  - image: image-2
    name: some-other-container
    resources:
      limits:
        cpu: 51m
        memory: 51Mi
      requests:
        cpu: 50m
        memory: 50Mi
`}},
		},
		{
			name: "merge_mutation_with_a_volume",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation volume",
					UpdateStrategy: MergeStrategy,
					Volumes:        []string{"some-ns/some-templ/volume_mutated1"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAVolume},
			want: []util.RenderItem{{
				Render: `spec:
  volumes:
  - configMap:
      name: istio
    name: volume_mutated1
`}},
		},
		{
			name: "merge_mutation_with_volumes",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation volume",
					UpdateStrategy: MergeStrategy,
					Volumes:        []string{"some-ns/some-templ/volume_mutated1", "some-ns/some-templ/volume_mutated2", "some-ns/some-templ/volume_mutated3"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithVolumes},
			want: []util.RenderItem{{
				Render: `spec:
  volumes:
  - configMap:
      name: istio
    name: volume_mutated1
`}, {
				Render: `spec:
  volumes:
  - hostPath:
      path: /data
      type: Directory
    name: volume_mutated2
`}, {
				Render: `spec:
  volumes:
  - name: volume_mutated3
    secret:
      optional: true
      secretName: mysecret
`}},
		},
		{
			name: "merge_mutation_with_volumes_with_intersections",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation volume",
					UpdateStrategy: MergeStrategy,
					Volumes:        []string{"some-ns/some-templ/volume_mutated1", "some-ns/some-templ/volume_mutated2"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithVolumesWithIntersections},
			want: []util.RenderItem{{
				Render: `spec:
  volumes:
  - configMap:
      name: istio
    name: volume_mutated1
`}, {
				Render: `spec:
  volumes:
  - configMap:
      name: istio-REPLACED
    name: volume_mutated1
    secret:
      optional: true
      secretName: mysecret
`}, {
				Render: `spec:
  volumes:
  - hostPath:
      path: /data
      type: Directory
    name: volume_mutated2
`}},
		},
		{
			name: "merge_mutation_with_an_annotation",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation annot",
					UpdateStrategy: MergeStrategy,
					Annotations:    []string{"some-ns/some-templ/annotation_mutated1"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAnAnnotation},
			want: []util.RenderItem{{
				Render: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation
`}},
		},
		{
			name: "replace_mutation_with_an_annotation",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation annot",
					UpdateStrategy: ReplaceStrategy,
					Annotations:    []string{"some-ns/some-templ/annotation_mutated1"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAnAnnotation},
			want: []util.RenderItem{{
				Render: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation
`}},
		},
		{
			name: "merge_mutation_with_annotations",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation annot",
					UpdateStrategy: MergeStrategy,
					Annotations: []string{"some-ns/some-templ/annotation_mutated1",
						"some-ns/some-templ/annotation_mutated2"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAnnotations},
			want: []util.RenderItem{
				{Render: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation
`},
				{Render: `metadata:
  annotations:
    annotation_mutated2: another-val-after-mutation
`}},
		},
		{
			name: "merge_mutation_with_annotations",
			args: args{
				config: v1alpha1.MutationConfig{
					Name:           "mutation annot",
					UpdateStrategy: ReplaceStrategy,
					Annotations: []string{"some-ns/some-templ/annotation_mutated1",
						"some-ns/some-templ/annotation_mutated2"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAnnotations},
			want: []util.RenderItem{
				{Render: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation
`},
				{Render: `metadata:
  annotations:
    annotation_mutated2: another-val-after-mutation
`}},
		},
		{
			name: "merge_mutation_with_annotations_with_adding",
			args: args{
				obj: map[string]interface{}{"metadata": map[string]interface{}{
					"annotations": map[string]interface{}{
						"annotation_mutated1": "val-after-mutation",
						"annot2":              "value2",
					},
				}},
				config: v1alpha1.MutationConfig{
					Name:           "mutation annot",
					UpdateStrategy: ReplaceStrategy,
					Annotations: []string{"some-ns/some-templ/annotation_mutated1",
						"some-ns/some-templ/annotation_mutated2"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAnnotations},
			want: []util.RenderItem{
				{Render: `metadata:
  annotations:
    annotation_mutated1: val-after-mutation
`},
				{Render: `metadata:
  annotations:
    annotation_mutated2: another-val-after-mutation
`}},
		},
		{
			name: "replace_mutation_adding_container", // replaced container not in obj
			args: args{
				obj: map[string]interface{}{"spec": map[string]interface{}{
					"containers": []interface{}{
						map[string]interface{}{
							"name":  "orig_to-test-latte",
							"image": "orig_img",
							"resources": map[string]interface{}{
								"limits":   map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
								"requests": map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
							},
						},
					},
				}},
				config: v1alpha1.MutationConfig{
					Name:           "mutation cfg",
					UpdateStrategy: ReplaceStrategy,
					Containers:     []string{"some-ns/some-templ/container_mutated"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAContainer},
			want: []util.RenderItem{{Render: `spec:
  containers:
  - image: orig_img
    name: orig_to-test-latte
    resources:
      limits:
        cpu: orig_100m
        memory: orig_100Mi
      requests:
        cpu: orig_100m
        memory: orig_100Mi
  - args:
    - arg1mut
    env:
    - name: env_name_mutated
      value: _mutated
    - name: ENV_10_mutated
      value: _mutated
    image: imagemut
    name: container_mutated
    resources:
      limits:
        cpu: 55_mutated
        memory: 55_mutated
      requests:
        cpu: 55_mutated
        memory: 55_mutated
    securityContext:
      capabilities:
        drop:
        - ALL_mutated
`}},
		},
		{
			name: "replace_mutation_replacing_container", // replaced container in obj
			args: args{
				obj: map[string]interface{}{"spec": map[string]interface{}{
					"containers": []interface{}{
						map[string]interface{}{
							"name":  "container_mutated",
							"image": "orig_img",
							"resources": map[string]interface{}{
								"limits":   map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
								"requests": map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
							},
						},
					},
				}},
				config: v1alpha1.MutationConfig{
					Name:           "mutation cfg",
					UpdateStrategy: ReplaceStrategy,
					Containers:     []string{"some-ns/some-templ/container_mutated"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAContainer},
			want: []util.RenderItem{{Render: `spec:
  containers:
  - args:
    - arg1mut
    env:
    - name: env_name_mutated
      value: _mutated
    - name: ENV_10_mutated
      value: _mutated
    image: imagemut
    name: container_mutated
    resources:
      limits:
        cpu: 55_mutated
        memory: 55_mutated
      requests:
        cpu: 55_mutated
        memory: 55_mutated
    securityContext:
      capabilities:
        drop:
        - ALL_mutated
`}},
		},
		{
			name: "replace_mutation_intersection_container", // replaced container in obj
			args: args{
				obj: map[string]interface{}{"spec": map[string]interface{}{
					"containers": []interface{}{
						map[string]interface{}{
							"name":  "container_mutated",
							"image": "orig_img",
							"resources": map[string]interface{}{
								"limits":   map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
								"requests": map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
							},
						},
					},
				}},
				config: v1alpha1.MutationConfig{
					Name:           "mutation cfg",
					UpdateStrategy: ReplaceStrategy,
					Containers: []string{"some-ns/some-templ/container_mutated",
						"some-ns/some-templ/some-other-container"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithContainersWithIntersections},
			want: []util.RenderItem{{
				Render: `spec:
  containers:
  - env:
    - name: ENV_1_ADD
      value: "false"
    - name: ENV_2
      value: "false"
    image: image-1
    name: container_mutated
`}, {Render: `spec:
  containers:
  - env:
    - name: ENV_1_ADD-REPLACED
      value: false-REPLACED
    - name: ENV_2-REPLACED
      value: false-REPLACED
    image: image-1-REPLACED
    name: container_mutated
`}, {Render: `spec:
  containers:
  - env:
    - name: ENV_1_ADD-REPLACED
      value: false-REPLACED
    - name: ENV_2-REPLACED
      value: false-REPLACED
    image: image-1-REPLACED
    name: container_mutated
  - image: image-2
    name: some-other-container
    resources:
      limits:
        cpu: 51m
        memory: 51Mi
      requests:
        cpu: 50m
        memory: 50Mi
`}},
		},
		{
			name: "replace_mutation_with_a_volume_adding_vol",
			args: args{
				obj: map[string]interface{}{"spec": map[string]interface{}{
					"volumes": []interface{}{
						map[string]interface{}{
							"name":  "volume_mutated",
							"image": "orig_img",
							"resources": map[string]interface{}{
								"limits":   map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
								"requests": map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
							},
						},
					},
				}},

				config: v1alpha1.MutationConfig{
					Name:           "mutation volume",
					UpdateStrategy: ReplaceStrategy,
					Volumes:        []string{"some-ns/some-templ/volume_mutated1"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAVolume},
			want: []util.RenderItem{{
				Render: `spec:
  volumes:
  - image: orig_img
    name: volume_mutated
    resources:
      limits:
        cpu: orig_100m
        memory: orig_100Mi
      requests:
        cpu: orig_100m
        memory: orig_100Mi
  - configMap:
      name: istio
    name: volume_mutated1
`}},
		},
		{
			name: "replace_mutation_with_a_volume_replace_vol",
			args: args{
				obj: map[string]interface{}{"spec": map[string]interface{}{
					"volumes": []interface{}{
						map[string]interface{}{
							"name":  "volume_mutated1",
							"image": "orig_img",
							"resources": map[string]interface{}{
								"limits":   map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
								"requests": map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
							},
						},
					},
				}},

				config: v1alpha1.MutationConfig{
					Name:           "mutation volume",
					UpdateStrategy: ReplaceStrategy,
					Volumes:        []string{"some-ns/some-templ/volume_mutated1"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAVolume},
			want: []util.RenderItem{{
				Render: `spec:
  volumes:
  - configMap:
      name: istio
    name: volume_mutated1
`}},
		},
		{
			name: "replace_mutation_with_a_volume_replace_vol",
			args: args{
				obj: map[string]interface{}{"spec": map[string]interface{}{
					"volumes": []interface{}{
						map[string]interface{}{
							"name":  "volume_mutated1",
							"image": "orig_img",
							"resources": map[string]interface{}{
								"limits":   map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
								"requests": map[string]interface{}{"cpu": "orig_100m", "memory": "orig_100Mi"},
							},
						},
					},
				}},

				config: v1alpha1.MutationConfig{
					Name:           "mutation volume",
					UpdateStrategy: ReplaceStrategy,
					Volumes:        []string{"some-ns/some-templ/volume_mutated1"},
				},
			},
			templates: map[string]v1alpha1.Template{"some-ns/some-templ": templateWithAVolumeWithIntersection},
			want: []util.RenderItem{{
				Render: `spec:
  volumes:
  - configMap:
      name: istio
    name: volume_mutated1
`}, {Render: `spec:
  volumes:
  - configMap:
      name: istio-REPLACED
    name: volume_mutated1
`}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logs.Logger = zap.NewNop()

			r := &MutateController{}
			got := r.getRendersForPod(tt.templates, tt.args.obj, tt.args.config)

			if len(got) != len(tt.want) {
				t.Fatalf("len got = %v, len want = %v.\ngot: %v\n",
					len(got), len(tt.want), got)
			}

			for i := range got {
				if !reflect.DeepEqual(got[i].Render, tt.want[i].Render) {
					t.Errorf("getRendersForPod()[%v].Render = \n%+v, want \n%+v", i, got[i].Render, tt.want[i].Render)
				}
			}
		})
	}
}

func Test_convert(t *testing.T) {
	type args struct {
		i interface{}
	}
	tests := []struct {
		name string
		args args
		want interface{}
	}{
		{
			name: "map string interface",
			args: args{
				i: map[string]interface{}{
					"metadata": map[string]interface{}{
						"test": "value",
					},
				},
			},
			want: map[string]interface{}{
				"metadata": map[string]interface{}{
					"test": "value",
				},
			},
		},
		{
			name: "map slice of interfaces",
			args: args{
				i: []interface{}{
					map[string]string{
						"test": "value",
					},
				},
			},
			want: []interface{}{
				map[string]string{"test": "value"},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := convert(tt.args.i); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("convert() = %v, want %v", got, tt.want)
			}
		})
	}
}
